package com.demo2do.fms.admin.service.supplier.impl;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.apache.poi.ss.usermodel.DataFormatter;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import com.demo2do.core.entity.exception.BusinessException;
import com.demo2do.core.entity.page.Page;
import com.demo2do.core.persistence.annotation.Dao;
import com.demo2do.core.utils.DateUtils;
import com.demo2do.fms.admin.Constants;
import com.demo2do.fms.admin.dao.supplier.DriverDao;
import com.demo2do.fms.admin.dao.supplier.SupplierDao;
import com.demo2do.fms.admin.dao.supplier.SupplierRelationDao;
import com.demo2do.fms.admin.dao.supplier.TractorEventDao;
import com.demo2do.fms.admin.dao.vehicle.RemoteTransMeansDao;
import com.demo2do.fms.admin.dao.vehicle.TractorDao;
import com.demo2do.fms.admin.dao.vehicle.TractorModifiedVersionDao;
import com.demo2do.fms.admin.dao.vehicle.VehicleDao;
import com.demo2do.fms.admin.dao.vehicle.VehicleSourceDao;
import com.demo2do.fms.admin.entity.report.supplier.TractorReportSearch;
import com.demo2do.fms.admin.entity.search.supplier.TractorSearch;
import com.demo2do.fms.admin.entity.security.Login;
import com.demo2do.fms.admin.entity.upload.UploadResult;
import com.demo2do.fms.admin.handler.FileHandler;
import com.demo2do.fms.admin.handler.VamHandler;
import com.demo2do.fms.admin.handler.support.FileStorage;
import com.demo2do.fms.admin.handler.support.FmsJdbcSupport;
import com.demo2do.fms.admin.service.DataServiceImpl;
import com.demo2do.fms.admin.service.supplier.TractorService;
import com.demo2do.fms.admin.utils.DateParseUtils;
import com.demo2do.fms.entity.authority.Organization;
import com.demo2do.fms.entity.driver.Driver;
import com.demo2do.fms.entity.driver.DriverAuditStatus;
import com.demo2do.fms.entity.supplier.common.Supplier;
import com.demo2do.fms.entity.vehicle.AuditStatus;
import com.demo2do.fms.entity.vehicle.DeclareStatus;
import com.demo2do.fms.entity.vehicle.ModifiedStatus;
import com.demo2do.fms.entity.vehicle.source.LockStatus;
import com.demo2do.fms.entity.vehicle.source.LockType;
import com.demo2do.fms.entity.vehicle.source.RemoteTransMeans;
import com.demo2do.fms.entity.vehicle.source.Vehicle;
import com.demo2do.fms.entity.vehicle.source.VehicleSource;
import com.demo2do.fms.entity.vehicle.source.VehicleSourceEvent;
import com.demo2do.fms.entity.vehicle.source.VehicleSourceEventType;
import com.demo2do.fms.entity.vehicle.source.VehicleSourceLockRecord;
import com.demo2do.fms.entity.vehicle.tractor.CarOwnership;
import com.demo2do.fms.entity.vehicle.tractor.Tractor;
import com.demo2do.fms.entity.vehicle.tractor.TractorEvent;
import com.demo2do.fms.entity.vehicle.tractor.TractorEventType;
import com.demo2do.fms.entity.vehicle.tractor.TractorModifiedVersion;
import com.demo2do.fms.entity.vehicle.tractor.TractorVehicleType;
import com.demo2do.fms.entity.vehicle.tractor.TransMean;
import com.demo2do.fms.entity.vehicle.trailer.Trailer;

/**
 *
 * @author Ark
 *
 */
@Service("tractorService")
public class TractorServiceImpl extends DataServiceImpl<Tractor> implements TractorService {
    
    private static final Logger logger = LoggerFactory.getLogger(TractorServiceImpl.class);

    @Dao
    private TractorDao tractorDao;
    
    @Dao
    private SupplierDao supplierDao;
    
    @Dao
    private SupplierRelationDao supplierRelationDao;
    
    @Dao
    private TractorModifiedVersionDao tractorModifiedVersionDao;
    
    @Dao
    private VehicleSourceDao vehicleSourceDao;
    
    @Dao
    private RemoteTransMeansDao remoteTransMeansDao;
    
    @Dao
    private VehicleDao vehicleDao;
    
    @Dao
    private DriverDao driverDao;
    
    @Dao
    private TractorEventDao tractorEventDao;
    
    @Autowired
    private FileHandler fileHandler;
    
    @Autowired
    private FmsJdbcSupport fmsJdbcSupport;
    
    @Autowired
    private VamHandler vamHandler;
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.supplier.TractorService#count(com.demo2do.fms.entity.vehicle.AuditStatus, com.demo2do.fms.admin.entity.security.Login)
     */
    public int count(Login login, AuditStatus status) {

        if (login.isSupplier()) {
            
            Supplier supplier = supplierDao.loadSupplier(login.getSupplierId());
            return tractorDao.count(supplier, status);
            
        } else {
            return tractorDao.count(status);
        }
        
    }

    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.supplier.TractorService#list(com.demo2do.fms.entity.vehicle.AuditStatus, com.demo2do.fms.admin.entity.security.Login, com.demo2do.core.entity.page.Page)
     */
    public List<Tractor> list(Login login, AuditStatus status, Page page) {
        
        page.calculate();
        
        if (login.isSupplier()) {

            Supplier supplier = supplierDao.loadSupplier(login.getSupplierId());
            return tractorDao.list(supplier, status, page.getBeginIndex(), page.getEveryPage());
            
        } else {
            return tractorDao.list(status, page.getBeginIndex(), page.getEveryPage());
        }
        
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.supplier.TractorService#count(com.demo2do.fms.admin.entity.search.supplier.TractorSearch)
     */
    public int count(TractorSearch search) {
        String sentence = search.buildSentenceTemplate();
        Map<String, Object> parameters = search.buildParameters();
        return genericDaoSupport.count(sentence, parameters);
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.supplier.TractorService#search(com.demo2do.fms.admin.entity.search.supplier.TractorSearch, com.demo2do.core.entity.page.Page)
     */
    @SuppressWarnings("unchecked")
    public List<Tractor> search(TractorSearch search, Page page) {
        
        // 1. build sentence
        String sentence = search.buildSentenceTemplate();
        Map<String, Object> parameters = search.buildParameters();
        
        // 2. do query
        List<Tractor> tractors = genericDaoSupport.searchForList(sentence, parameters, page.getBeginIndex(), page.getEveryPage());
        page.calculate();
        return tractors;
        
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.supplier.TractorService#create(com.demo2do.fms.entity.vehicle.Tractor, com.demo2do.fms.admin.entity.security.Login)
     */
    public void create(Tractor tractor, Login login) {

        Date now = new Date();
        Long operator = login.getId();
        
        // 1. validate
        if (!login.isSupplier()) {
            throw BusinessException.newInstance("500", "非供方，无法创建车辆");
        }
        
        String validMessage = this.checkUniqueField(tractor.getBrandCNCode(), tractor.getSimCard());
        if (StringUtils.isNotBlank(validMessage)) {
            throw BusinessException.newInstance("400", validMessage);
        }
        
        // 2. save tractor
        Supplier supplier = supplierDao.loadSupplier(login.getSupplierId());
        Organization organization = genericDaoSupport.load(Organization.class, supplier.getOrgId());
        
        tractor = tractor.initialize(organization, supplier, now, operator);
        tractor = this.syncOldTractor(tractor);
        genericDaoSupport.save(tractor);
        
        // 3. save tractor_event
        genericDaoSupport.save(new TractorEvent(tractor, TractorEventType.CREATE, operator));
        
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.supplier.TractorService#upload(org.springframework.web.multipart.MultipartFile, com.demo2do.fms.admin.entity.security.Login)
     */
    @SuppressWarnings("unchecked")
    public List<UploadResult> upload(MultipartFile file, Login login) {
        
        try {

            // 1. save file
            FileStorage fileStorage = new FileStorage("vehicle/upload", Constants.TRACTOR_BATCH_UPLOAD, FilenameUtils.getExtension(file.getOriginalFilename()));
            fileHandler.save(fileStorage, file.getInputStream());
            
            // 2. save tractor && tractor_event
            Map<String, Object> result = this.parse(file, login);
            List<Tractor> tractors = (List<Tractor>) result.get("tractors");
            for (Tractor tractor : tractors) {
                
                // 2.1 save tractor
                genericDaoSupport.save(tractor);
                
                // 2.2 save tractor_event
                genericDaoSupport.save(new TractorEvent(tractor, TractorEventType.CREATE, login.getId()));
                
            }
            
            // 3. return upload result
            logger.trace("#upload() - batch import tractor success, number = [{}]", tractors.size());
            return (List<UploadResult>) result.get("uploadResults");
            
        } catch (Exception e) {
            logger.error("#upload() - batch import tractor fail, exception = [{}]", e);
            throw BusinessException.newInstance("500", "批量上传失败，" + e.getMessage());
        }
        
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.supplier.TractorService#exist(java.lang.String, java.lang.String, java.lang.String)
     */
    public Map<String, Object> exist(String category, String brandCNCode, String simCard) {
        
        Map<String, Object> result = new HashMap<String, Object>();
        boolean exist = true;
        String message = null;
        
        if (StringUtils.equalsIgnoreCase("brandCNCode", category)) {
            
            Tractor tractor = tractorDao.getByBrandCNCode(brandCNCode);
            if (tractor == null) exist = false;
            
        } else if (StringUtils.equalsIgnoreCase("simCard", category)) {
            
            Tractor tractor = tractorDao.getBySimcard(simCard);
            if (tractor == null) {
                exist = false;
            } else {
                Supplier supplier = tractor.getSupplier();
                message = "该SIM卡已存在! 所属供方：" + supplier.getShortName() + "，所属车辆：" + tractor.getBrandCNCode();
            }
            
        } else {
            throw BusinessException.newInstance("400", "无效参数");
        }
        
        result.put("exist", exist);
        result.put("message", message);
        return result;
        
    }
    
    /**
     * 校验牵引车唯一性
     * 
     * @param brandCnCode
     * @param simCard
     * @return
     */
    private String checkUniqueField(String brandCnCode, String simCard) {
        
        // 1. check tractor brand cn_code
        if (StringUtils.isNotBlank(brandCnCode)) {
            Tractor tractor = tractorDao.getByBrandCNCode(brandCnCode);
            if (tractor != null) {
                Supplier supplier = tractor.getSupplier();
                return "该牵引车中文车牌号已存在！车辆所属供方：" + supplier.getCode();
            }
        }
        
        // 2. check vehicle simcard
        if (StringUtils.isNotBlank(simCard)) {
            Tractor tractor = tractorDao.getBySimcard(simCard);
            if (tractor != null) {
                Supplier supplier = tractor.getSupplier();
                return "该SIM卡已存在！车辆所属供方：" + supplier.getCode() + "！ 车辆牵引车中文车牌号：" + tractor.getBrandCNCode();
            }
        }
        
        return null;
        
    }
    
    /**
     * sync old tractor info
     * 
     * @param tractor
     * @return
     */
    private Tractor syncOldTractor(Tractor tractor) {
        
        String brandCNCode = tractor.getBrandCNCode();
        
        Tractor existTractor = tractorDao.getByBrandCNCode(brandCNCode, true);
        if(existTractor != null && StringUtils.isNotBlank(existTractor.getCode())) {
            
            // 同步老车辆信息
            tractor.editCode(existTractor.getCode());
            
        }
        
        return tractor;
        
    }
    
    /**
     * @param file
     * @param login
     * @return
     */
    public Map<String, Object> parse(MultipartFile file, Login login) {
        
        Map<String, Object> result = new HashMap<String, Object>();
        
        List<Tractor> tractors = new ArrayList<Tractor>();
        List<UploadResult> uploadResults = new ArrayList<UploadResult>();
        
        // 0. prepare data
        Long operator = login.getId();
        Supplier supplier = supplierDao.loadSupplier(login.getSupplierId());
        Organization organization = genericDaoSupport.load(Organization.class, supplier.getOrgId());
        
        DataFormatter formatter = new DataFormatter();
        
        try {
            
            logger.trace("#parse() - Starting parsing upload file.");
    
            // 1. get workbook
            Workbook workbook = WorkbookFactory.create(file.getInputStream());
            
            // 2. get sheet and rows
            Sheet sheet = workbook.getSheetAt(0);
            Iterator<Row> rows = sheet.iterator();
            
            // 3. read rows and transform to tractor
            while (rows.hasNext()) {
                
                Row row = rows.next();
                int rowNum = row.getRowNum();
                UploadResult uploadResult = null;
                
                if (rowNum < 2) {
                    continue;
                }
                
                String floorLord = formatter.formatCellValue(row.getCell(0));       // 产证所有者
                CarOwnership carOwnership = CarOwnership.descriptionOf(formatter.formatCellValue(row.getCell(1)));  // 车辆所属权
                TransMean transMean = TransMean.descriptionOf(formatter.formatCellValue(row.getCell(2)));           // 运输工具类型
                
                String brandCNCode = formatter.formatCellValue(row.getCell(3));        // 牵引车中文车牌
                if (StringUtils.isBlank(brandCNCode)) {      // 判断Excel空行情况
                    continue;
                }
                if (!Pattern.matches("[\u4e00-\u9fa5]{1}[\u4e00-\u9fa5a-zA-Z0-9]*", brandCNCode)) {
                    uploadResults.add(new UploadResult(rowNum, "牵引车中文车牌号填写与模板不符"));
                    continue;
                }
                
                String brandENCode = formatter.formatCellValue(row.getCell(4));        // 牵引车英文文车牌
                if (!Pattern.matches("[A-Za-z0-9]+", brandENCode)) {
                    uploadResults.add(new UploadResult(rowNum, "牵引车英文车牌号填写与模板不符"));
                    continue;
                }
                
                String vincode = formatter.formatCellValue(row.getCell(5));       // 牵引车vin码
                String brand = formatter.formatCellValue(row.getCell(6));       // 牵引车品牌
                
                String horsepowerText = formatter.formatCellValue(row.getCell(7));       // 牵引车马力
                if (StringUtils.isBlank(horsepowerText) || !NumberUtils.isCreatable(horsepowerText) || !horsepowerText.matches("[0-9]+")) {
                    uploadResults.add(new UploadResult(rowNum, "牵引车马力填写与模板不符"));
                    continue;
                }
                BigDecimal horsepower = new BigDecimal(horsepowerText);
                
//                String gps = formatter.formatCellValue(row.getCell(8));       // GPS品牌
//                String simCard = formatter.formatCellValue(row.getCell(9));       // GPS绑定SIM卡
                
                String issuingTimeText = formatter.formatCellValue(row.getCell(8));       // 发证时间
                if (StringUtils.isBlank(issuingTimeText) || !DateParseUtils.validDate(issuingTimeText, Constants.DATE_YYYYMMDD) || !StringUtils.equals(issuingTimeText, DateFormatUtils.format(com.demo2do.core.utils.DateUtils.parseDate(issuingTimeText, "yyyyMMdd"), "yyyyMMdd"))) {
                    uploadResults.add(new UploadResult(rowNum, "发证时间填写与模板不符"));
                    continue;
                }
                Date issuingTime = DateUtils.parseDate(issuingTimeText, Constants.DATE_YYYYMMDD);
                
                String registrationTimeText = formatter.formatCellValue(row.getCell(9));       // 注册时间
                if (StringUtils.isBlank(registrationTimeText) || !DateParseUtils.validDate(registrationTimeText, Constants.DATE_YYYYMMDD) || !StringUtils.equals(registrationTimeText, DateFormatUtils.format(com.demo2do.core.utils.DateUtils.parseDate(registrationTimeText, "yyyyMMdd"), "yyyyMMdd"))) {
                    uploadResults.add(new UploadResult(rowNum, "注册时间填写与模板不符"));
                    continue;
                }
                Date registrationTime = DateUtils.parseDate(registrationTimeText, Constants.DATE_YYYYMMDD);
                
                String axlesText = formatter.formatCellValue(row.getCell(10));       // 轴数
                if (!NumberUtils.isCreatable(axlesText)) {
                    uploadResults.add(new UploadResult(rowNum, "轴数填写与模板不符"));
                    continue;
                }
                BigDecimal axles = new BigDecimal(axlesText);
                
                String annualInspectionDateText = formatter.formatCellValue(row.getCell(11));       // 年检有效期至
                if (StringUtils.isBlank(annualInspectionDateText) || !DateParseUtils.validDate(annualInspectionDateText, Constants.DATE_YYYYMM) || !StringUtils.equals(annualInspectionDateText, DateFormatUtils.format(com.demo2do.core.utils.DateUtils.parseDate(annualInspectionDateText, "yyyyMM"), "yyyyMM"))) {
                    uploadResults.add(new UploadResult(rowNum, "年检有效期至填写与模板不符"));
                    continue;
                }
                Date annualInspectionDate = DateUtils.parseDate(annualInspectionDateText, Constants.DATE_YYYYMM);
                
                String trainloadText = formatter.formatCellValue(row.getCell(12));       // 装载量
                if (StringUtils.isBlank(trainloadText) || !NumberUtils.isCreatable(trainloadText)) {
                    uploadResults.add(new UploadResult(rowNum, "装载量填写与模板不符"));
                    continue;
                }
                BigDecimal trainload = new BigDecimal(trainloadText);
                
                TractorVehicleType vehicleType = TractorVehicleType.descriptionOf(formatter.formatCellValue(row.getCell(13)));       // 车辆类型（行驶证）
                
                // 4. construct tractor
//                Tractor tractor = new Tractor(floorLord, carOwnership, transMean, brandCNCode, brandENCode, vincode, brand, horsepower, gps, simCard, issuingTime, registrationTime, axles, annualInspectionDate, trainload, vehicleType, organization, supplier, operator);
                Tractor tractor = new Tractor(floorLord, carOwnership, transMean, brandCNCode, brandENCode, vincode, brand, horsepower, issuingTime, registrationTime, axles, annualInspectionDate, trainload, vehicleType, organization, supplier, operator);

                // 5.1 check required field
                uploadResult = this.checkRequiredField(rowNum, tractor);
                if (uploadResult != null) {
                    uploadResults.add(uploadResult);
                    continue;
                }
                
                // 5.2 check unique field
//                uploadResult = this.checkUniqueField(rowNum, brandCNCode, simCard);
                uploadResult = this.checkUniqueField(rowNum, brandCNCode);
                if (uploadResult != null) {
                    uploadResults.add(uploadResult);
                    continue;
                }
                
                // 5.3 check unique field of excel
//                uploadResult = this.checkUniqueFieldOfExcel(tractors, rowNum, brandCNCode, simCard);
                uploadResult = this.checkUniqueFieldOfExcel(tractors, rowNum, brandCNCode);
                if (uploadResult != null) {
                    uploadResults.add(uploadResult);
                    continue;
                }
                
                // 6. sync old tractor & put to list
                tractor = this.syncOldTractor(tractor);
                tractors.add(tractor);
                
            }
            
            logger.trace("#parse() - Finishing parsing file with {} records.", tractors.size());
            
        } catch (Exception e) {
            logger.error("#parse() - Finishing parsing file with errors.", e);
        }
        
        result.put("tractors", tractors);
        if (org.apache.commons.collections.CollectionUtils.isNotEmpty(uploadResults)) {
            result.put("uploadResults", uploadResults);
        }
        
        return result;
        
    }

    /**
     * @param rowNum
     * @param tractor
     * @return
     */
    private UploadResult checkRequiredField(int rowNum, Tractor tractor) {
        
        if (StringUtils.isBlank(tractor.getFloorLord())) {
            return new UploadResult(rowNum, "产证所有者填写与模板不符");
        } else if (tractor.getCarOwnership() == null) {
            return new UploadResult(rowNum, "车辆所属权填写与模板不符");
        } else if (tractor.getTransMean() == null) {
            return new UploadResult(rowNum, "运输工具类型填写与模板不符");
        } else if(StringUtils.isBlank(tractor.getBrandENCode())) {
            return new UploadResult(rowNum, "牵引车英文车牌号填写与模板不符");
        } else if (StringUtils.isBlank(tractor.getVincode())) {
            return new UploadResult(rowNum, "牵引车VIN号填写与模板不符");
        } else if (StringUtils.isBlank(tractor.getBrand())) {
            return new UploadResult(rowNum, "牵引车品牌填写与模板不符");
        } else if (tractor.getVehicleType() == null) {
            return new UploadResult(rowNum, "车辆类型（行驶证）填写与模板不符");
        }
        
        return null;
        
    }

    /**
     * @param rowNum
     * @param brandCNCode
     * @param simCard
     * @return
     */
    private UploadResult checkUniqueField(int rowNum, String brandCNCode, String simCard) {
    
        Tractor tractor = tractorDao.getByBrandCNCode(brandCNCode);
        if (tractor != null) {
            Supplier supplier = tractor.getSupplier();
            String supplierInfo = com.demo2do.core.utils.StringUtils.connect(supplier.getCode(), " - ", supplier.getName());
            return new UploadResult(rowNum, "牵引车中文牌号已存在", supplierInfo, tractor.getBrandCNCode());
        }
    
        tractor = tractorDao.getBySimcard(simCard);
        if (tractor != null) {
            Supplier supplier = tractor.getSupplier();
            String supplierInfo = com.demo2do.core.utils.StringUtils.connect(supplier.getCode(), " - ", supplier.getName());
            return new UploadResult(rowNum, "该SIM卡号已存在", supplierInfo, tractor.getBrandCNCode());
        }
        
        return null;
        
    }

    /**
     * @param rowNum
     * @param brandCNCode
     * @return
     */
    private UploadResult checkUniqueField(int rowNum, String brandCNCode) {

        Tractor tractor = tractorDao.getByBrandCNCode(brandCNCode);
        if (tractor != null) {
            Supplier supplier = tractor.getSupplier();
            String supplierInfo = com.demo2do.core.utils.StringUtils.connect(supplier.getCode(), " - ", supplier.getName());
            return new UploadResult(rowNum, "牵引车中文牌号已存在", supplierInfo, tractor.getBrandCNCode());
        }

        return null;

    }
    
    /**
     * @param tractors
     * @param rowNum
     * @param brandCNCode
     * @param simCard
     * @return
     */
    @SuppressWarnings("unchecked")
    private UploadResult checkUniqueFieldOfExcel(List<Tractor> tractors, int rowNum, String brandCNCode, String simCard) {
        
        if (CollectionUtils.isEmpty(tractors)) {
            return null;
        }
        
        List<String> brandCNCodes = com.demo2do.core.utils.CollectionUtils.extractPropertyList(tractors, "brandCNCode");
        if (brandCNCodes.contains(brandCNCode)) {
            return new UploadResult(rowNum, "请不要填写相同中文车牌号");
        }
        
        List<String> simCards = com.demo2do.core.utils.CollectionUtils.extractPropertyList(tractors, "simCard");
        if (simCards.contains(simCard)) {
            return new UploadResult(rowNum, "请不要填写相同SIM卡号");
        }
    
        return null;
        
    }

    /**
     * @param tractors
     * @param rowNum
     * @param brandCNCode
     * @return
     */
    @SuppressWarnings("unchecked")
    private UploadResult checkUniqueFieldOfExcel(List<Tractor> tractors, int rowNum, String brandCNCode) {

        if (CollectionUtils.isEmpty(tractors)) {
            return null;
        }

        List<String> brandCNCodes = com.demo2do.core.utils.CollectionUtils.extractPropertyList(tractors, "brandCNCode");
        if (brandCNCodes.contains(brandCNCode)) {
            return new UploadResult(rowNum, "请不要填写相同中文车牌号");
        }

        return null;

    }

    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.supplier.TractorService#audit(com.demo2do.fms.entity.vehicle.Tractor, java.lang.Long)
     */
    public void audit(Tractor tractor, Long operator) {
        
        // 1. validate
        String auditMessage = tractor.auditMessage();
        if (StringUtils.isNotBlank(auditMessage)) {
            throw BusinessException.newInstance("500", auditMessage);
        }
        
        auditMessage = this.checkUniqueField(tractor);
        if (StringUtils.isNotBlank(auditMessage)) {
            throw BusinessException.newInstance("500", auditMessage);
        }
        
        // 2. submit audit
        genericDaoSupport.update(tractor.audit(operator));
        genericDaoSupport.save(new TractorEvent(tractor, TractorEventType.AUDIT, operator));
        
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.supplier.TractorService#batchAudit(java.util.List, java.lang.Long)
     */
    public int batchAudit(List<Long> ids, Long operator) {
        
        int failNumber = 0;
        
        List<Tractor> tractors = tractorDao.list(ids);
        for (Tractor tractor : tractors) {
            
            try {
        
                this.audit(tractor, operator);
                
            } catch (Exception e) {
                failNumber++;
                logger.error("#batchAudit() - audit failed, tractorId=[{}], exception=[{}]", tractor.getId(), e);
            }
            
        }
        
        return failNumber;
        
    }
    
    /**
     * @param tractor
     * @return
     */
    private String checkUniqueField(Tractor tractor) {
        
        // 中文车牌号
        int count = tractorDao.countByBrandCNCode(tractor.getBrandCNCode());
        if (count > 1) {
            return "车辆中文车牌号重复";
        }
        
        // sim卡号
        if (StringUtils.isNotBlank(tractor.getSimCard())) {
            count = tractorDao.countBySimCard(tractor.getSimCard());
            if (count > 1) {
                return "车辆SIM卡号重复";
            }
        }
        
        return null;
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.supplier.TractorService#editInfo(com.demo2do.fms.entity.vehicle.Tractor, java.lang.Long)
     */
    public void editInfo(Tractor tractor, Long operator) {
        Date now = new Date();
        genericDaoSupport.update(tractor.operate(now, operator));
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.supplier.TractorService#editGps(com.demo2do.fms.entity.vehicle.Tractor, java.lang.Long)
     */
    public void editGps(Tractor tractor, Long operator) {
        
        Date now = new Date();
        
        // 1. update tractor
        genericDaoSupport.update(tractor.operate(now, operator));
        
        // 2. update gps of remote_trans_means
        if (tractor.isSynchronize()) {
            String code = tractor.getCode();
            if (StringUtils.isNotBlank(code)) {
                RemoteTransMeans remoteTransMeans = remoteTransMeansDao.loadByCode(code);
                if(remoteTransMeans != null) {
                    genericDaoSupport.update(remoteTransMeans.editGps(tractor.getSimCard(), now));
                }
            }
        }
        
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.supplier.TractorService#delete(com.demo2do.fms.entity.vehicle.Tractor, java.lang.Long)
     */
    public void delete(Tractor tractor, Long operator) {
        
        Date now = new Date();
        
        // 1 delete tractor
        genericDaoSupport.update(tractor.disable(operator));
        genericDaoSupport.save(new TractorEvent(tractor, TractorEventType.DELETE, operator));
        
        if (tractor.isBind()) {
            
            // 2. delete vehicle_source
            VehicleSource vehicleSource = vehicleSourceDao.getByTractorCnCode(tractor.getBrandCNCode());
            genericDaoSupport.update(vehicleSource.disable(operator).operate(now, operator));
            genericDaoSupport.save(new VehicleSourceEvent(vehicleSource, VehicleSourceEventType.UNBIND, operator));
            
            // 3. unbind trailer
            if (vehicleSource.hasTrailer()) {
                Trailer trailer = genericDaoSupport.load(Trailer.class, vehicleSource.getTrailerId());
                genericDaoSupport.update(trailer.unbind().operate(now, operator));
            }
            
        }
        
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.supplier.TractorService#prepareChange(com.demo2do.fms.entity.vehicle.Tractor)
     */
    public TractorModifiedVersion prepareChange(Tractor tractor) {
        
        // 1. load最新未审核版本号
        TractorModifiedVersion lastedVersion = tractorModifiedVersionDao.load(tractor.getId(), ModifiedStatus.AUDITING);
        int version = lastedVersion == null ? 1 : lastedVersion.getVersionSorting() + 1;
        
        // 2. create version by tractor
        TractorModifiedVersion currentVersion = new TractorModifiedVersion(tractor, version);
        genericDaoSupport.save(currentVersion);
        
        return currentVersion;
        
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.supplier.TractorService#change(com.demo2do.fms.entity.vehicle.Tractor, com.demo2do.fms.entity.vehicle.TractorModifiedVersion, java.lang.Long)
     */
    public void change(Tractor tractor, TractorModifiedVersion version, Long operator) {
        
        // 0. check tractor_trainload
        VehicleSource vehicleSource = vehicleSourceDao.loadByTractor(tractor.getId());
        if (vehicleSource != null && !vehicleSource.hasTrailer() && version.requireTrailer()) {
            throw BusinessException.newInstance("500", "车头装载量为0时，必须绑定挂车");
        }
        
        // 1. change tractor
        genericDaoSupport.update(tractor.change(operator));
        genericDaoSupport.save(new TractorEvent(tractor, TractorEventType.CHANGE, operator));
        
        // 2. update version
        genericDaoSupport.update(version);
        
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.supplier.TractorService#loadModifiedVersion(com.demo2do.fms.entity.vehicle.Tractor)
     */
    public TractorModifiedVersion loadModifiedVersion(Tractor tractor) {
        return tractorModifiedVersionDao.load(tractor.getId(), ModifiedStatus.AUDITING);
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.supplier.TractorService#transfer(com.demo2do.fms.entity.vehicle.Tractor, com.demo2do.fms.entity.supplier.common.Supplier, java.lang.Long)
     */
    public void transfer(Tractor tractor, Supplier supplier, Long operator) {
        
        Date now = new Date();
        Organization organization = genericDaoSupport.load(Organization.class, supplier.getOrgId());
        
        // 3. handle trailer 、 vehicle_source
        if (tractor.isBind()) {
            
            VehicleSource vehicleSource = vehicleSourceDao.loadByTractor(tractor.getId());      // 删除整车
            genericDaoSupport.update(vehicleSource.disable(operator).operate(now, operator));
            genericDaoSupport.save(new VehicleSourceEvent(vehicleSource, VehicleSourceEventType.UNBIND, operator));
            
            if (vehicleSource.hasTrailer()) {                                           // 解绑挂车
                Trailer trailer = genericDaoSupport.load(Trailer.class, vehicleSource.getTrailerId());
                genericDaoSupport.update(trailer.unbind().operate(now, operator));
            }
            
            if(vehicleSource.isSynchronize()) {                                                 // 解除司机和车辆绑定关系
                this.lockVehicle(vehicleSource, "转籍牵引车冻结", LockType.FREEZE, operator, "转籍牵引车冻结");
            }
        }
        
        // 1. transfer tractor
        genericDaoSupport.update(tractor.transfer(supplier, organization, operator));
        
        // 2. save tractor_event
        tractorEventDao.update(supplier, tractor);
        genericDaoSupport.save(new TractorEvent(tractor, TractorEventType.TRANSFER, operator));
        
        
        
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.supplier.TractorService#out(com.demo2do.fms.entity.vehicle.Tractor, java.lang.Long)
     */
    public void out(Tractor tractor, Long operator) {

        Date now = new Date();

        // 1. 判断是否存在已同步整车
        VehicleSource vehicleSource = vehicleSourceDao.loadByTractor(tractor.getId());

        if (vehicleSource != null && vehicleSource.isSynchronize()) {

            // 1.1 锁定MDM车辆
            this.lockVehicle(vehicleSource, "转出牵引车冻结", LockType.FREEZE, operator, "转出冻结");

            // 1.2 删除司机和车辆绑定关系
            Vehicle vehicle = vehicleDao.loadByCode(vehicleSource.getCode());
            if(vehicle != null) {
                List<Driver> drivers = driverDao.listByVehicle(vehicle.getId(), DriverAuditStatus.QUALIFIED);
                for(Driver driver : drivers) {
                    genericDaoSupport.update(driver.unbind(operator, now));
                }
            }
            
        }
        
        // 2.1 转出整车
        if (tractor.isBind()) {
            
            genericDaoSupport.update(vehicleSource.out(operator));
            genericDaoSupport.save(new VehicleSourceEvent(vehicleSource, VehicleSourceEventType.UNBIND, operator));
            
            // 2.2 解绑挂车
            if (vehicleSource.hasTrailer()) {
                Trailer trailer = genericDaoSupport.load(Trailer.class, vehicleSource.getTrailerId());
                genericDaoSupport.update(trailer.unbind().operate(now, operator));
            }
            
        }
        
        // 3. 转出牵引车
        if (tractor.isSynchronize()){

            // 1. lock fms vehicle

            String vehicleCode = tractor.getCode();

            RemoteTransMeans remoteTransMeans = remoteTransMeansDao.loadByCode(vehicleCode);

            if(remoteTransMeans == null) {
                throw new BusinessException("该车辆在MDM中不存在");
            }
            if(!remoteTransMeans.isAvaliable()) {
                throw new BusinessException("MDM中该车辆为非正常车辆");
            }

            // 2. lock mdm vehicle
            genericDaoSupport.update(remoteTransMeans.lock(LockType.FREEZE, Constants.MDM_LOCK_USERID));

        }
        // select * from remotetarnsmeans where code in ( ) where  locktype != freeze
        // select code from fms_tractor t where  bind = false and status = out and code is not null
        genericDaoSupport.update(tractor.out(operator));
        genericDaoSupport.save(new TractorEvent(tractor, TractorEventType.OUT, operator));
        
    }

    /**
     * @param vehicleSource
     * @param reason
     * @param type
     * @param operator
     * @param description
     */
    private void lockVehicle(VehicleSource vehicleSource, String reason, LockType type, Long operator, String description) {
        
        Date now = new Date();
        
        // 1. lock fms vehicle
        genericDaoSupport.update(vehicleSource.doLock(reason, type, operator, now));
        
        String vehicleCode = vehicleSource.getCode();
        RemoteTransMeans remoteTransMeans = remoteTransMeansDao.loadByCode(vehicleCode);
        
        if(remoteTransMeans == null) {
            throw new BusinessException("该车辆在MDM中不存在");
        }
        if(!remoteTransMeans.isAvaliable()) {
            throw new BusinessException("MDM中该车辆为非正常车辆");
        }
        
        // 2. lock mdm vehicle
        genericDaoSupport.update(remoteTransMeans.lock(type, Constants.MDM_LOCK_USERID));
        
        // 3. create lock record
        VehicleSourceLockRecord record = new VehicleSourceLockRecord(vehicleSource, LockStatus.LOCKED, description, operator);
        genericDaoSupport.save(record);
        
    }
    
    /**
     * @param vehicleSource
     * @param operator
     * @param description
     */
    private void unlockVehicle(VehicleSource vehicleSource, Long operator, String description) {
        
        Date now = new Date();
        
        // 1. unlock fms vehicle
        genericDaoSupport.update(vehicleSource.unlock(operator, now));
        
        String vehicleCode = vehicleSource.getCode();
        RemoteTransMeans remoteTransMeans = remoteTransMeansDao.loadByCode(vehicleCode);
        
        if(remoteTransMeans == null) {
            throw new BusinessException("该车辆在MDM中不存在");
        }
        if(!remoteTransMeans.isAvaliable()) {
            throw new BusinessException("MDM中该车辆为非正常车辆");
        }
        
        // 2. lock mdm vehicle
        genericDaoSupport.update(remoteTransMeans.unlock(Constants.MDM_LOCK_USERID));
        
        // 3. create lock record
        VehicleSourceLockRecord record = new VehicleSourceLockRecord(vehicleSource, LockStatus.UNLOCKED, description, operator);
        genericDaoSupport.save(record);
        
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.supplier.TractorService#count(com.demo2do.fms.admin.entity.security.Login)
     */
    public int count(Login login) {
        
        if (login.isSupplier()) {
            
            Supplier supplier = genericDaoSupport.load(Supplier.class, login.getSupplierId());
            return tractorDao.count(supplier);
            
        } else {
            return tractorDao.count();
        }
        
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.supplier.TractorService#list(com.demo2do.fms.admin.entity.security.Login, com.demo2do.core.entity.page.Page)
     */
    public List<Tractor> list(Login login, Page page) {
        
        page.calculate();
        
        if (login.isSupplier()) {
            
            Supplier supplier = genericDaoSupport.load(Supplier.class, login.getSupplierId());
            return tractorDao.list(supplier, page.getBeginIndex(), page.getEveryPage());
            
        } else {
            return tractorDao.list(page.getBeginIndex(), page.getEveryPage());
        }
        
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.supplier.TractorService#export(com.demo2do.fms.admin.entity.report.supplier.TractorReportSearch)
     */
    public List<Map<String, Object>> export(TractorReportSearch search) {
        String sentence = search.buildSentenceTemplate();
        Map<String, Object> parameters = search.buildParameters();
        RowMapper<Map<String, Object>> rowMapper = search.buildRowMapper();
        return fmsJdbcSupport.queryForList(sentence, parameters, rowMapper);
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.supplier.TractorService#countSub(com.demo2do.fms.admin.entity.security.Login)
     */
    public int countSub(Login login) {
        
        if (!login.isSupplier()) {  // 总部或分区用户, 拿所有整车
            
            return tractorDao.count();
            
        } else {                    // 一级供应商， 拿所属二级供应商整车
            
            Supplier supplier = genericDaoSupport.load(Supplier.class, login.getSupplierId());
            List<Long> supplierIds = supplierRelationDao.listSponsoredSupplierId(supplier);
            
            if (org.apache.commons.collections.CollectionUtils.isEmpty(supplierIds)) {
                return 0;
            }
            return tractorDao.countBySupplierIds(supplierIds);
            
        }
        
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.supplier.TractorService#listSub(com.demo2do.fms.admin.entity.security.Login, com.demo2do.core.entity.page.Page)
     */
    public List<Tractor> listSub(Login login, Page page) {
        
        page.calculate();
        
        if (!login.isSupplier()) {  // 总部或分区用户, 拿所有整车
            
            return tractorDao.list(page.getBeginIndex(), page.getEveryPage());
            
        } else {                    // 一级供应商， 拿所属二级供应商整车
            
            Supplier supplier = genericDaoSupport.load(Supplier.class, login.getSupplierId());
            List<Long> supplierIds = supplierRelationDao.listSponsoredSupplierId(supplier);
            return tractorDao.listBySupplierIds(supplierIds, page.getBeginIndex(), page.getEveryPage());
            
        }
        
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.supplier.TractorService#countSub(com.demo2do.fms.admin.entity.search.supplier.TractorSearch, com.demo2do.fms.admin.entity.security.Login)
     */
    public int countSub(TractorSearch search, Login login) {
        
        if (login.isSupplier()) {
            Supplier loginSupplier = genericDaoSupport.load(Supplier.class, login.getSupplierId());
            List<Long> supplierIds = supplierRelationDao.listSponsoredSupplierId(loginSupplier);
            search.addSupplierId(supplierIds);
        }
        
        // 1. build sentence
        String sentence = search.buildSentenceTemplate();
        Map<String, Object> parameters = search.buildParameters();
        
        // 2. do query
        return genericDaoSupport.count(sentence, parameters);
        
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.supplier.TractorService#searchSub(com.demo2do.fms.admin.entity.search.supplier.TractorSearch, com.demo2do.fms.admin.entity.security.Login, com.demo2do.core.entity.page.Page)
     */
    @SuppressWarnings("unchecked")
    public List<Tractor> searchSub(TractorSearch search, Login login, Page page) {
        
        if (login.isSupplier()) {
            Supplier loginSupplier = genericDaoSupport.load(Supplier.class, login.getSupplierId());
            List<Long> supplierIds = supplierRelationDao.listSponsoredSupplierId(loginSupplier);
            search.addSupplierId(supplierIds);
        }
        
        // 1. build sentence
        String sentence = search.buildSentenceTemplate();
        Map<String, Object> parameters = search.buildParameters();
        
        // 2. do query
        List<Tractor> tractors = genericDaoSupport.searchForList(sentence, parameters, page.getBeginIndex(), page.getEveryPage());
        page.calculate();
        return tractors;
        
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.supplier.TractorService#countAudit(com.demo2do.fms.admin.entity.security.Login, com.demo2do.fms.entity.vehicle.DeclareStatus)
     */
    public int countAudit(Login login, DeclareStatus declareStatus) {
        
        List<AuditStatus> statuses = Arrays.asList(AuditStatus.AUDITING, AuditStatus.MODIFIED);
        
        if (!login.isSupplier()) {  // 非供方用户, 拿所有供应商下整车
            
            return tractorDao.count(statuses, declareStatus);
            
        } else {                    // 一级供应商， 拿所属二级供应商整车
            
            Supplier supplier = genericDaoSupport.load(Supplier.class, login.getSupplierId());
            List<Long> supplierIds = supplierRelationDao.listSponsoredSupplierId(supplier);
            if (org.apache.commons.collections.CollectionUtils.isEmpty(supplierIds)) {
                return 0;
            }
            return tractorDao.countBySupplierIds(supplierIds, statuses, declareStatus);
            
        }
        
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.supplier.TractorService#listAudit(com.demo2do.fms.admin.entity.security.Login, com.demo2do.fms.entity.vehicle.DeclareStatus, com.demo2do.core.entity.page.Page)
     */
    public List<Tractor> listAudit(Login login, DeclareStatus declareStatus, Page page) {
        
        List<AuditStatus> statuses = Arrays.asList(AuditStatus.AUDITING, AuditStatus.MODIFIED);
        
        page.calculate();
        
        if (!login.isSupplier()) {  // 非供方用户, 拿所有牵引车
            
            return tractorDao.list(statuses, declareStatus, page.getBeginIndex(), page.getEveryPage());
            
        } else {                    // 一级供应商， 拿所属二级供应商整车
            
            Supplier supplier = genericDaoSupport.load(Supplier.class, login.getSupplierId());
            List<Long> supplierIds = supplierRelationDao.listSponsoredSupplierId(supplier);
            return tractorDao.listBySupplierIds(supplierIds, statuses, declareStatus, page.getBeginIndex(), page.getEveryPage());
            
        }
        
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.supplier.TractorService#adoptAuditing(com.demo2do.fms.entity.vehicle.Tractor, java.lang.Long)
     */
    public void adoptAuditing(Tractor tractor, Long operator) {
        
        // 1. adopt audit
        genericDaoSupport.update(tractor.adoptAuditing(operator));
        
        // 2. save tractor_event
        genericDaoSupport.save(new TractorEvent(tractor, TractorEventType.AUDIT_ADOPT, operator));
        
        if (tractor.isVam()) {
            
            // 3.2 unlock vehicle_source
            VehicleSource vehicleSource = vehicleSourceDao.loadByTractor(tractor.getId());
            if (vehicleSource != null && vehicleSource.couldUnlock()) {
                Trailer trailer = genericDaoSupport.get(Trailer.class, vehicleSource.getTrailerId());
                if (trailer.getStatus().isQualified() || trailer.getStatus().isModified()) {
                    this.unlockVehicle(vehicleSource, operator, "牵引车审核通过解锁");
                }
            }
            
            // 3.3 send info to vam
            vamHandler.syncVehicleStatus("tractor", tractor, null);
            
        }
        
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.supplier.TractorService#adoptModified(com.demo2do.fms.entity.vehicle.Tractor, java.lang.Long)
     */
    public void adoptModified(Tractor tractor, Long operator) {
    
        // 1. 更新牵引车基本信息
        TractorModifiedVersion version = tractorModifiedVersionDao.load(tractor.getId(), ModifiedStatus.AUDITING);
        genericDaoSupport.update(tractor.adoptModified(version, operator));
        
        // 2. adopt change
        tractorModifiedVersionDao.update(ModifiedStatus.QUALIFIED, tractor.getId(), ModifiedStatus.AUDITING);
        
        // 3. save tractor_event
        genericDaoSupport.save(new TractorEvent(tractor, TractorEventType.CHANGE_ADOPT, operator));
        
        // 4. send status_info to vam
        if (tractor.isVam()) {
            vamHandler.syncVehicleStatus("tractor", tractor, null);
        }
        
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.supplier.TractorService#rejectAudit(com.demo2do.fms.entity.vehicle.Tractor, java.lang.Long)
     */
    public void rejectAudit(Tractor tractor, Long operator) {
        
        // 1. reject audit
        genericDaoSupport.update(tractor.rejectAuditing(operator));
        
        // 2. save tractor_event
        genericDaoSupport.save(new TractorEvent(tractor, TractorEventType.AUDIT_REJECT, operator));
        
        // 3. send status_info to vam
        if (tractor.isVam()) {
            vamHandler.syncVehicleStatus("tractor", tractor, null);
        }
        
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.supplier.TractorService#rejectModified(com.demo2do.fms.entity.vehicle.Tractor, java.lang.Long)
     */
    public void rejectModified(Tractor tractor, Long operator) {
        
        // 1. 更新牵引车状态为驳回
        genericDaoSupport.update(tractor.rejectAuditing(operator));
        
        // 2. reject change
        tractorModifiedVersionDao.update(ModifiedStatus.REJECTED, tractor.getId(), ModifiedStatus.AUDITING);
        
        // 3. save tractor_event
        genericDaoSupport.save(new TractorEvent(tractor, TractorEventType.CHANGE_REJECT, operator));
        
        // 4. send status_info to vam
        if (tractor.isVam()) {
            vamHandler.syncVehicleStatus("tractor", tractor, null);
        }
        
    }
    
    /* (non-Javadoc)
     * @see com.demo2do.fms.admin.service.supplier.TractorService#listUnbind(com.demo2do.fms.entity.supplier.common.Supplier, com.demo2do.fms.entity.vehicle.AuditStatus)
     */
    public List<Tractor> listUnbind(Supplier supplier, AuditStatus status) {
        return tractorDao.list(supplier, status, false);
    }
    
}
